#include "LynxAudioManager_C.hpp"
#include "LynxSound_C.hpp"
#include "LynxSoundSource_C.hpp"

#include "LynxAudioReaderSine_C.hpp"

#define THROW_INVALID_STATE if(!mActive) THROW_EXCEPTION(LEC_INVALID_STATE, "No active manager.")

namespace Lynx
{
namespace Audio
{

AudioManager_C::AudioManager_C(LogManager *manager) :
    mDevice(NULL),
    mContext(NULL),
    mLogManager(manager),
    mDebugMonoLoader(NULL),
    mDebugStereoLoader(NULL),
    mActive(false),
    mDeviceName("")
{
    mLogManager->increment_counter();

    mDebugMonoLoader = new AudioReaderLoaderMonoSine_C();
    mDebugStereoLoader = new AudioReaderLoaderStereoSine_C();

    const char* c = alcGetString(NULL, ALC_DEVICE_SPECIFIER);

    while(*c != '\0')
    {
        mDevices.push_back(std::string(c));
        c += strlen(c) + 1;
    }
}

AudioManager_C::~AudioManager_C()
{
    if(mActive)
        deactivate_manager();

    mLogManager->decrement_counter();
}

bool AudioManager_C::is_manager_active(void) const
{
    return mActive;
}

bool AudioManager_C::activate_manager(const char *device_name)
{
    if(mActive)
        deactivate_manager();

    mDevice = alcOpenDevice(device_name);

    if(mDevice == NULL && device_name != NULL)
        THROW_EXCEPTION(LEC_INVALID_PARAMETER, "Couldn't open the specified device.");
    else if(mDevice == NULL)
        THROW_EXCEPTION(LEC_INTERNAL_ERROR, "Couldn't open the standard device.");

    mContext = alcCreateContext(mDevice, 0);

    if(mContext == NULL)
        THROW_EXCEPTION(LEC_INTERNAL_ERROR, "Couldn't create a context.");
    else if(!alcMakeContextCurrent(mContext))
        THROW_EXCEPTION(LEC_INTERNAL_ERROR, "Couldn't set the current context.");

    mDeviceName = device_name ? device_name : alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
    mActive = true;

    return true;
}

void AudioManager_C::deactivate_manager(void)
{
    THROW_INVALID_STATE;

    shutdown_engine();
}

std::string AudioManager_C::get_manager_name(void) const
{
    THROW_INVALID_STATE;

    return mDeviceName;
}

std::vector<std::string> AudioManager_C::get_possible_device_names(void) const
{
    return mDevices;
}

SoundSource *AudioManager_C::create_static_source(Myth::Stream::Input *input, const std::string &name)
{
    THROW_INVALID_STATE;

    return NULL;
}

SoundSource *AudioManager_C::create_streaming_source(Myth::Stream::Input *input, const std::string &name)
{
    THROW_INVALID_STATE;

    return NULL;
}

SoundSource *AudioManager_C::create_debug_mono_source(const std::string &name)
{
    THROW_INVALID_STATE;

    SoundSource *s = new SoundSource_C(this, mDebugMonoLoader->create_audio_reader(0), false, name);
    mSources.push_back(s);
    return s;
}

SoundSource *AudioManager_C::create_debug_stereo_source(const std::string &name)
{
    THROW_INVALID_STATE;

    SoundSource *s = new SoundSource_C(this, mDebugStereoLoader->create_audio_reader(0), false, name);
    mSources.push_back(s);
    return s;
}

uint32_t AudioManager_C::get_source_amount(void) const
{
    THROW_INVALID_STATE;

    return mSources.size();
}

SoundSource *AudioManager_C::get_source(uint32_t index) const
{
    THROW_INVALID_STATE;

    if(index >= mSources.size())
        THROW_EXCEPTION(LEC_PARAMETER_OUT_OF_RANGE, "The source index is too damn high!");

    std::list<SoundSource*>::const_iterator it = mSources.begin();
    while(index > 0)
        it++;

    return *it;
}

SoundSource *AudioManager_C::get_source(const std::string &name) const
{
    THROW_INVALID_STATE;

    for(std::list<SoundSource*>::const_iterator it = mSources.begin(); it != mSources.end(); it++)
        if((*it)->get_name() == name)
            return (*it);

    return NULL;
}

bool AudioManager_C::remove_source(uint32_t index)
{
    THROW_INVALID_STATE;

    if(index >= mSources.size())
        THROW_EXCEPTION(LEC_PARAMETER_OUT_OF_RANGE, "The source index is too damn high!");

    std::list<SoundSource*>::iterator it = mSources.begin();
    for(uint32_t a = 0; a < index; a++)
        it++;

    mSources.erase(it);
}

bool AudioManager_C::remove_source(const std::string &name)
{
    THROW_INVALID_STATE;

    SoundSource *s = get_source(name);

    if(!s)
        THROW_EXCEPTION(LEC_INVALID_PARAMETER, "Can't find the source.");

    return s->decrement_counter();
}

bool AudioManager_C::remove_all_sources(void)
{
    THROW_INVALID_STATE;

    bool removed = true;

    for(std::list<SoundSource*>::iterator it = mSources.begin(); it != mSources.end(); it++)
        if(!(*it)->decrement_counter())
            removed = false;

    mSources.clear();
    return removed;
}

Sound *AudioManager_C::create_sound(SoundSource *source, bool play_looped, bool start_paused)
{
    THROW_INVALID_STATE;

    Sound *s = new Sound_C(source, play_looped, start_paused);

    mSounds.push_back(s);
    return s;
}

Sound *AudioManager_C::create_sound(SoundSource *source, Myth::Math::Vector3f pos, bool play_looped, bool start_paused)
{
    THROW_INVALID_STATE;

    return NULL;
}

void AudioManager_C::play_all_sounds()
{
    THROW_INVALID_STATE;

    ALuint *buffer = new ALuint[mSounds.size()];
    uint32_t a = 0;

    for(std::list<Sound*>::iterator it = mSounds.begin(); it != mSounds.end(); a++, it++)
        buffer[a] = ((Sound_C*)*it)->get_internal_identifier();

    alSourcePlayv(mSounds.size(), buffer);
}

void AudioManager_C::pause_all_sounds()
{
    THROW_INVALID_STATE;

    ALuint *buffer = new ALuint[mSounds.size()];
    uint32_t a = 0;

    for(std::list<Sound*>::iterator it = mSounds.begin(); it != mSounds.end(); a++, it++)
        buffer[a] = ((Sound_C*)*it)->get_internal_identifier();

    alSourcePausev(mSounds.size(), buffer);
}

void AudioManager_C::stop_all_sounds(void)
{
    THROW_INVALID_STATE;

    ALuint *buffer = new ALuint[mSounds.size()];
    uint32_t a = 0;

    for(std::list<Sound*>::iterator it = mSounds.begin(); it != mSounds.end(); a++, it++)
        buffer[a] = ((Sound_C*)*it)->get_internal_identifier();

    alSourceStopv(mSounds.size(), buffer);
}


uint32_t AudioManager_C::get_sound_amount(void) const
{
    //
}


Sound *AudioManager_C::get_sound(uint32_t index) const
{
    //
}


bool AudioManager_C::remove_sound(uint32_t index)
{
    //
}


bool AudioManager_C::remove_all_sounds(void)
{
    THROW_INVALID_STATE;

    stop_all_sounds();
    bool removed = true;

    for(std::list<Sound*>::iterator it = mSounds.begin(); it != mSounds.end(); it++)
        if(!(*it)->decrement_counter())
            removed = false;

    mSounds.clear();
    return removed;
}


void AudioManager_C::add_audio_reader_loader(AudioReaderLoader *loader)
{
    mLoaders.push_back(loader);
}

void AudioManager_C::shutdown_engine()
{
    if(mActive)
        remove_all_sources();

    if(mContext)
    {
        if(!alcMakeContextCurrent(0))
            THROW_EXCEPTION(LEC_INTERNAL_ERROR, "Couldn't unset the current context.");

        alcDestroyContext(mContext);
        mContext = NULL;
    }

    if(mDevice)
    {
        if(!alcCloseDevice(mDevice))
            THROW_EXCEPTION(LEC_INTERNAL_ERROR, "Couldn't close the current device.");

        mDevice = NULL;
    }

    mDeviceName = "";
    mActive = false;
}

////////////////////////////////////////////////////////////////////////////
void AudioManager_C::internal_remove_source(SoundSource *source)
{
    for(std::list<SoundSource*>::iterator it = mSources.begin(); it != mSources.end(); it++)
    {
        if(*it == source)
        {
            mSources.erase(it);
            return;
        }
    }

    THROW_EXCEPTION(LEC_INTERNAL_ERROR, "Tried removing source that didn't exist anymore.");
}

} // namespace Audio
} // namespace Lynx
